

import java.util.ArrayList;
import java.util.Random;

public class Timer implements Runnable
{
	//variables
	private int unchokedNeighborCount;
	private int unchokeIntervalms;
	private int optimisticUnchokeIntervalms;
	
	//for choking
	private long lastUnchoke;
	private long lastOptimisticUnchoke;
	private boolean closeNow;

	public Timer(int unchokedNeighbors, int unchokeInterval, int optimisticUnchokeInterval) throws Exception
	{
		this.unchokedNeighborCount = unchokedNeighbors;
		this.unchokeIntervalms = unchokeInterval*1000;
		this.optimisticUnchokeIntervalms = optimisticUnchokeInterval*1000;
		this.closeNow = false;
	}
	
	public void run()
	{
		try
		{
			initalize();
			while(true)
			{
				update();
				Thread.yield();
			}
		}
		catch(Exception e) 
		{
			System.out.println(e);
			e.printStackTrace();
		}
	}
	
	public void initalize()
	{
		//Initialize Timers
		lastUnchoke = System.currentTimeMillis();
		lastOptimisticUnchoke = System.currentTimeMillis();
	}
	
	public void update()
	{
		//if every peer finished the download, prepare to close
		if(PeerProcess.incompletePeers.size() == 0 && !closeNow)
		{
			//PeerProcess.logEntry("ALL PEERS COMPLETED DOWNLOAD");
			
			int size = PeerProcess.indexByID.size();
			for(int i=0; i<size; i++)
			{
				PeerProcess.haveMsgsToTransmit.get(i).add(new Integer(PeerProcess.NumberOfPieces+2));
			}
			
			if(PeerProcess.hasEnded == false)
				PeerProcess.hasEnded = true;
			
			//Joins all the files together
			PeerProcess.mySplitter.join();
			
			closeNow = true;
			//Wait 5 seconds to make sure all messages are transmitted
			try
			{
				for(int i=0; i<5; i++)
				{
					//PeerProcess.logEntry("closing in "+(5-i)+" seconds");
					Thread.sleep(1000);
				}
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}

		//If every peer finished the download and we waited for the rest of the packets to send
		if(closeNow)
		{
			//Terminates the program
			System.exit(0);
		}
		
		//if the unchoke interval occured
		if((System.currentTimeMillis() - lastUnchoke) > this.unchokeIntervalms)
		{
			lastUnchoke = System.currentTimeMillis();
			int[] pieceCount = new int[PeerProcess.NumberOfPreferredNeighbors];
			int[] ID = new int[PeerProcess.NumberOfPreferredNeighbors];
			
			for(int i=0; i < PeerProcess.NumberOfPreferredNeighbors; i++)
			{
				pieceCount[i] = 0;
				ID[i] = -1;
			}
			
			//determine unchokedNeighborCount max uploaders
			for(int i=0; i<PeerProcess.InterestedNeighbors.size(); i++)
			{
				int id = PeerProcess.InterestedNeighbors.get(i);	//gets ID of interested peer
				int index = PeerProcess.indexByID.indexOf(new Integer(id));	//gets their index in receivedChunks
				int current = PeerProcess.receivedChunks.get(index);	//gets the amount of chunks they send us
				int j = 0;
				//update top neighbors
				while(j < PeerProcess.NumberOfPreferredNeighbors && current >= pieceCount[j])
				{
					if(j==0)
					{
						pieceCount[j] = current;
						ID[j] = id;
					}
					else
					{
						int temp1 = pieceCount[j];
						int temp2 = ID[j];
						pieceCount[j] = pieceCount[j-1];
						ID[j] = ID[j-1];
						pieceCount[j-1] = temp1;
						ID[j-1] = temp2;
					}
					j++;
				}
			}
			//store those in new array list
			String toLog = "";
			ArrayList<Integer> newUnchoke = new ArrayList<Integer>();
			
			//Sets up the new Unchoke list and String to log
			boolean has = false;
			int z = 0;
			while(!has && z < ID.length)
			{
				if(ID[z] >= 0)
				{
					newUnchoke.add(new Integer(ID[z]));
					toLog += ID[z];
					has = true;
				}
				z++;
			}
			for(int i=z; i<PeerProcess.NumberOfPreferredNeighbors; i++)
			{
				if(ID[i] >= 0)
				{
					newUnchoke.add(new Integer(ID[i]));
					toLog += ", " + ID[i];
				}
			}
			
			//reset all upload counts
			for(int i=0; i<PeerProcess.receivedChunks.size(); i++)
			{
				PeerProcess.receivedChunks.set(i, new Integer(0));
			}

			//Writes the log Entry
			if(has)
				PeerProcess.logEntry("has the preferred neighbors " + toLog);

			//Clears the previous unchoked neighbor list
			PeerProcess.UnChokedNeighbors.clear();
			//Deep Copy adds each new unchoked neighbor
			for(int j=0; j < newUnchoke.size(); j++)
			{
				PeerProcess.UnChokedNeighbors.add(new Integer(newUnchoke.get(j)));
			}
		}

		//If optimistic unchoke interval occured
		if((System.currentTimeMillis() - lastOptimisticUnchoke) > this.optimisticUnchokeIntervalms)
		{
			lastOptimisticUnchoke = System.currentTimeMillis();
			
			//Finds all interested Neighbors
			boolean selected = false;
			ArrayList<Integer> newOptUnchoke = new ArrayList<Integer>();
			for(int i=0; i<PeerProcess.InterestedNeighbors.size(); i++)
			{
				int id = PeerProcess.InterestedNeighbors.get(i);
				if(!PeerProcess.UnChokedNeighbors.contains(new Integer(id)))
				{
					newOptUnchoke.add(new Integer(id));
					selected = true;
				}
			}
			//unchokes a random interested neighbor
			if(selected)
			{
				Random rand = new Random();
				int index = rand.nextInt(newOptUnchoke.size());
				PeerProcess.OptimisticUnchokedNeighbor = newOptUnchoke.get(index);
			}
			else
				PeerProcess.OptimisticUnchokedNeighbor = -1;
			
			//log
			if(PeerProcess.OptimisticUnchokedNeighbor != -1)
				PeerProcess.logEntry("has the optimistically unchoked neighbor " + PeerProcess.OptimisticUnchokedNeighbor);
		}
	}
}
